/*
 * Copyright (c) 2012 OptiCare Systems Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors: Kevin Martin (kev82@khn.org.uk)
 */
#include <string>
#include <libgearman/gearman.h>
#include <cstring>
#include <stdexcept>
#include <cassert>
#include <boost/shared_ptr.hpp>
#include <syslog.h>

#include "gmw.hpp"

void *workerfunction(gearman_job_st *job, void *context,
 size_t *result_size, gearman_return_t *ret_ptr) {
	*result_size = 0;
	*ret_ptr = GEARMAN_FATAL;

	if(context == NULL) {
		return NULL;
	}

	GearmanWorkerFunction &gw = *(reinterpret_cast<GearmanWorkerFunction *>(context));
	{
		const char *workload = NULL;
		size_t wlen = 0;
		workload = reinterpret_cast<const char *>(gearman_job_workload(job));
		wlen = gearman_job_workload_size(job);

		std::string data = "";

		if(workload != NULL && wlen > 0) {
			//don;t copy the null terminator if it is already there
			if(workload[wlen-1] == 0) --wlen;
			data = std::string(workload, wlen);
		}

		if(!gw.implementation(data)) {
			//we need to log the worker failure somewhere
			//Can we do it with the job_st instead?
			syslog(LOG_ERR, gw.getReturn().c_str());
			return NULL;
		}
	}

	int len = gw.getReturn().length()+1;
	char *rv = (char *)malloc(len);
	strncpy(rv, gw.getReturn().c_str(), len);
	*result_size = len;
	*ret_ptr = GEARMAN_SUCCESS;
	return rv;
}

_GearmanWorkerStruct_::_GearmanWorkerStruct_() {
	gw_ = gearman_worker_create(NULL);
	if(gw_ == NULL) {
		throw std::runtime_error("Couldn't create worker");
	}
}

_GearmanWorkerStruct_::~_GearmanWorkerStruct_() {
	assert(gw_ != NULL);
	gearman_worker_free(gw_);
}

void GearmanWorker::addServer() {
	if(gearman_worker_add_server(gw_, host_.c_str(), port_)
	 != GEARMAN_SUCCESS) {
		throw std::runtime_error("Failed to add server");
	}
}

void GearmanWorker::registerFunction() {
	for(int i=0;i!=wfs_.size();++i) {
		boost::shared_ptr<GearmanWorkerFunction> wf = wfs_[i];
		if(gearman_worker_add_function(gw_, wf->name().c_str(), 0, workerfunction,
		 (void *)(wf.get())) != GEARMAN_SUCCESS) {
			throw std::runtime_error("Failed to add function");
		}
	}
}

void GearmanWorker::unregisterAll() {
	if(gearman_worker_unregister_all(gw_) != GEARMAN_SUCCESS) {
		throw std::runtime_error("Failed to unregister function");
	}
}

void GearmanWorker::runloop() {
	while(true) {
		gearman_return_t rv = gearman_worker_work(gw_);
		if(rv == GEARMAN_SUCCESS || rv == GEARMAN_TIMEOUT) continue;
		throw std::runtime_error("Gearman worker work failed");
	}
}

GearmanWorker::GearmanWorker(boost::shared_ptr<GearmanWorkerFunction> wf) :
 host_("127.0.0.1"), port_(4730), terminate_(false) {
	wfs_.push_back(wf);
	addServer();
}

GearmanWorker::GearmanWorker(const std::vector<boost::shared_ptr<GearmanWorkerFunction> > &wfs) :
 wfs_(wfs), host_("127.0.0.1"), port_(4730), terminate_(false) {
	addServer();
}

void GearmanWorker::Run() {
	registerFunction();
	runloop();
	unregisterAll();
}
